
namespace Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;

    /// <summary>
    /// Main function class that provides a lightweight proxy interface of TFS Source Control Object Model
    /// </summary>
    [Serializable]
    public class WorkspaceProxy
    {
        #region Private members and constructor

        private const char SERVER_PATH_DELIMETER = '/';

        private ConnectionManagerProxy tfsConnectionProxy;
        
        [NonSerialized]
        TeamProject project;
        [NonSerialized]
        private Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer versionControlServer;
        [NonSerialized]
        private Microsoft.TeamFoundation.VersionControl.Client.Workspace workspace;

        //Events queue
        [NonSerialized]
        private static Queue<GetCommandOutput> GET_Queue = new Queue<GetCommandOutput>();
        [NonSerialized]
        private static Queue<NonFatalErrorOutput> ERROR_Queue = new Queue<NonFatalErrorOutput>();
        [NonSerialized]
        private static Queue<CommitCheckInOutput> COMMIT_Queue = new Queue<CommitCheckInOutput>();
        [NonSerialized]
        private static Queue<ConflictOutput> CONFLICT_Queue = new Queue<ConflictOutput>();
        [NonSerialized]
        private static Queue<PendingChangeOutput> PENDINGCHANGE_Queue = new Queue<PendingChangeOutput>();

        #endregion

        private TfsTeamProjectCollection TeamProjectCollection
        {
            get
            {
                if (this.tfsConnectionProxy == null)
                    throw new InvalidOperationException("this.tfsConnectionProxy is null. Object badly initialized.");

                if (this.tfsConnectionProxy.TeamProjectCollection == null)
                    throw new InvalidOperationException("this.tfsConnectionProxy.TeamProjectCollection is null. Object badly initialized.");

                return this.tfsConnectionProxy.TeamProjectCollection;
            }
        }

        #region Public methods and properties

        /// <summary>
        /// Initialize Tfs client connection with workspace and working folder mapped
        /// </summary>
        /// <param name="connectionProxy">Object holding the connection to TeamProjectCollection</param>
        /// <param name="workspacePath">Path of the Tfs workspace</param>
        /// <param name="branchPath">The path of the branch within the Team Project</param>
        public void Init(ConnectionManagerProxy connectionProxy, string workspacePath, string branchPath)
        {
            if (connectionProxy == null)
                throw new ArgumentNullException("connectionProxy");

            if (String.IsNullOrEmpty(workspacePath))
                throw new ArgumentNullException("workspacePath");

            this.tfsConnectionProxy = connectionProxy;

            // versioncontrolserver setup
            this.versionControlServer = (VersionControlServer)connectionProxy.TeamProjectCollection.GetService(typeof(VersionControlServer));

            // Listen for the Source Control events
            this.versionControlServer.NonFatalError += WorkspaceProxy.OnNonFatalError;
            this.versionControlServer.Getting += WorkspaceProxy.OnGetting;
            this.versionControlServer.CommitCheckin += WorkspaceProxy.OnCommitCheckin;
            this.versionControlServer.Conflict += WorkspaceProxy.OnConflict;
            this.versionControlServer.UndonePendingChange += WorkspaceProxy.OnUndonePendingChange;
            this.versionControlServer.NewPendingChange += WorkspaceProxy.OnNewPendingChange;

            // Check a workspace
            this.workspace = TryGetWorkspace(workspacePath);

            if (this.workspace == null)
            {
                // Populate existing workspaces
                WorkspaceInfo[] wsInfo = Workstation.Current.GetAllLocalWorkspaceInfo();
                int workspaceCount = wsInfo == null ? 0 : wsInfo.Length;
                // Create a workspace, every new workspace path will create a new workspace with name like workstation_AXWORKSPACE0, workstationAXWORKSPACE1 etc.
                string workspaceName = String.Format(CultureInfo.InvariantCulture, "{0}_AXWORKSPACE{1}", Workstation.Current.Name, workspaceCount);
                this.workspace = versionControlServer.CreateWorkspace(workspaceName, versionControlServer.AuthorizedUser);

                // Check project does exist on server
                this.project = this.versionControlServer.GetTeamProject(this.tfsConnectionProxy.ProjectName);

                // Create a mapping.
                this.workspace.CreateMapping(new WorkingFolder(project.ServerItem, workspacePath));
            }
            else
            {
                this.versionControlServer = this.workspace.VersionControlServer;
                
                // Check project does exist on server, throws a VersionControlException if it doesn't exist
                this.project = versionControlServer.GetTeamProject(this.tfsConnectionProxy.ProjectName);
            }

			// Create a mapping.
            string serverPath = CombineServerPaths(project.ServerItem, branchPath);
            this.workspace.CreateMapping(new WorkingFolder(serverPath, workspacePath));
        }

        /// <summary>
        /// Initialize Tfs client connection with workspace and working folder mapped
        /// </summary>
        /// <param name="connectionProxy">Object holding the connection to TeamProjectCollection</param>
        /// <param name="workspacePath">Path of the Tfs workspace</param>
        public void Init(ConnectionManagerProxy connectionProxy, string workspacePath)
        {
            Init(connectionProxy, workspacePath, string.Empty);
        }


        /// <summary>
        /// Check in the given pending change with a comment
        /// </summary>
        /// <param name="change">Given pending change</param>
        /// <param name="comment">Comment</param>
        /// <returns>CheckInCommandResult</returns>
        public CheckInCommandResult CheckIn(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.Change change, string comment)
        {
            if (change == null)
                throw new ArgumentNullException("change");

            ChangeList changeList = new ChangeList();
            changeList.Add(change);

            return this.CheckIn(changeList, comment);
        }

        /// <summary>
        /// Check in the given pending change list with a comment
        /// </summary>
        /// <param name="changeList">Given pending change list</param>
        /// <param name="comment">Comment</param>
        /// <param name="workItemChanges">Work item changes</param>
        /// <returns>CheckInCommandResult</returns>
        private CheckInCommandResult CheckIn(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.ChangeList changeList, string comment, WorkItemCheckinInfo[] workItemChanges)
        {
            if (changeList == null)
                throw new ArgumentNullException("changeList");

            Debug.Assert(changeList.Count > 0, "Empty change list to check in is invalid.");
            PendingChange[] changes = this.SearchAndConvert(changeList);
            WorkspaceProxy.COMMIT_Queue.Clear();
            WorkspaceProxy.CONFLICT_Queue.Clear();
            CheckInCommandResult result = new CheckInCommandResult();

            result.ChangeSet = this.workspace.CheckIn(changes, comment, null, workItemChanges, null);

            //CommitCheckIn event
            result.CommitCheckInOutput = WorkspaceProxy.COMMIT_Queue.Dequeue();

            return result;
        }

        /// <summary>
        /// Check in the given pending change list with a comment
        /// </summary>
        /// <param name="changeList">Given pending change list</param>
        /// <param name="comment">Comment</param>
        /// <returns>CheckInCommandResult</returns>
        public CheckInCommandResult CheckIn(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.ChangeList changeList, string comment)
        {
            return CheckIn(changeList, comment, new WorkItemCheckinInfo[0]);
        }

        /// <summary>
        /// Check in the given pending change list with a comment
        /// </summary>
        /// <param name="changeList">Given pending change list</param>
        /// <param name="comment">Comment</param>
        /// <param name="workItemCheckInInfoList">The list of Work Item objects associated with the check-in</param>
        /// <returns>CheckInCommandResult</returns>
        public CheckInCommandResult CheckIn(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.ChangeList changeList, string comment, WorkItemCheckInInfoProxyCollection workItemCheckInInfoList)
        {
            WorkItemCheckinInfo[] checkInInfo = workItemCheckInInfoList.GetWorkItemCheckinInfoArray();
            return CheckIn(changeList, comment, checkInInfo);
        }

        /// <summary>
        /// 
        /// </summary>
        public bool Resolve(string serverPath, ConflictResolution resolution)
        {
            bool isResolved = true;
            Conflict[] conflictArray = this.workspace.QueryConflicts(new String[] { serverPath }, true);
            if (conflictArray != null && conflictArray.Length > 0)
            {
                foreach (Conflict conflict in conflictArray)
                {
                    Conflict[] resolvedConflicts;
                    conflict.Resolution = (Resolution)resolution;
                    this.workspace.ResolveConflict(conflict, out resolvedConflicts);

                    foreach (Conflict c in resolvedConflicts)
                    {
                        isResolved = c.IsResolved;
                    }
                }
            }
            return isResolved;
        }
        /// <summary>
        /// LastCheckInError
        /// </summary>
        public CheckInError LastCheckInError
        {
            get
            {
                CheckInError error = new CheckInError();
                //Conflict event
                foreach (ConflictOutput output in WorkspaceProxy.CONFLICT_Queue)
                {
                    error.Add(output);
                }

                return error;
            }

        }
        /// <summary>
        /// Check out items based on given change list
        /// </summary>
        /// <param name="changeList">Given change list</param>
        /// <returns>PendOperationCommandResult</returns>
        public PendOperationCommandResult CheckOut(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.ChangeList changeList)
        {
            if (changeList == null)
                throw new ArgumentNullException("changeList");

            Debug.Assert(changeList.Count > 0, "Empty change list to check out is invalid.");

            List<string> paths = new List<string>(changeList.Count);
            foreach (Change change in changeList)
            {
                Debug.Assert(!String.IsNullOrEmpty(change.LocalItem), "LocalItem can not be null or empty.");
                paths.Add(change.LocalItem);
            }

            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();
            result.NumberOfOperations = this.workspace.PendEdit(paths.ToArray(), RecursionType.Full, null, LockLevel.None);
            //PendEdit use NonFatalError event
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }
            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }
            return result;

        }

        /// <summary>
        /// Check out item based on give file path
        /// </summary>
        /// <param name="path">Given file path</param>
        /// <returns>PendOperationCommandResult</returns>
        public PendOperationCommandResult CheckOut(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();
            result.NumberOfOperations = this.workspace.PendEdit(path, RecursionType.None);
            //PendEdit use NonFatalError event
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }
            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }

            return result;
        }

        /// <summary>
        /// Undo check out item based on give file path
        /// </summary>
        /// <param name="path">Given file path</param>
        /// <returns>PendingChangeResult</returns>
        public PendOperationCommandResult UndoCheckOut(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();
            result.NumberOfOperations = this.workspace.Undo(path, RecursionType.None);
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }

            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }

            return result;
        }
        /// <summary>
        /// Get current checked out items
        /// </summary>
        /// <returns>Pending change list</returns>
        public ChangeList GetCheckedOutItems()
        {
            ChangeList changeList = new ChangeList();

            foreach (PendingChange change in this.workspace.GetPendingChanges())
            {
                changeList.Add(new Change(this.workspace, change));
            }

            return changeList;
        }
        /// <summary>
        /// Add item to pending change list
        /// </summary>
        /// <param name="change">Given item</param>
        /// <returns>PendOperationCommandResult</returns>
        public PendOperationCommandResult Add(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.Change change)
        {
            if (change == null)
                throw new ArgumentNullException("change");


            Debug.Assert(!String.IsNullOrEmpty(change.LocalItem), "LocalItem can not be null or empty.");

            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();

            result.NumberOfOperations = this.workspace.PendAdd(change.LocalItem);

            //PendAdd use NonFatalError Event
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }
            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }
            return result;
        }

        /// <summary>
        /// Rename a item
        /// </summary>
        /// <param name="oldPath">item path to rename</param>
        /// <param name="newPath">new item path</param>
        /// <returns>PendOperationCommandResult</returns>
        public PendOperationCommandResult Rename(string oldPath, string newPath)
        {
            if (String.IsNullOrEmpty(oldPath))
                throw new ArgumentNullException("oldPath");
            if (String.IsNullOrEmpty(newPath))
                throw new ArgumentNullException("newPath");

            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();

            result.NumberOfOperations = this.workspace.PendRename(oldPath, newPath);

            //PendRename use NonFatalError Event
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }
            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }
            return result;
        }

        //note: path is the local file name from AX
        /// <summary>
        /// Get history of given item path
        /// </summary>
        /// <param name="path">Path of the itme</param>
        /// <returns>Pending change list</returns>
        public ChangeList GetItemHistory(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            ChangeList list = new ChangeList();
            if (this.workspace.IsLocalPathMapped(path))
            {
                IEnumerable changeSetsEnum = this.versionControlServer.QueryHistory(path, LatestVersionSpec.Latest,
                    0, RecursionType.None, null, null, null, Int32.MaxValue, true, false);

                try
                {
                    foreach (Changeset cs in changeSetsEnum)
                    {
                        list.Add(cs);
                    }
                }
                catch (Microsoft.TeamFoundation.VersionControl.Client.ItemNotFoundException)
                {
                    //it is ok to catch this exception in case item is added to version control but not checked in yet.
                }
            }
            return list;
        }

        /// <summary>
        /// Get history of given directory path
        /// </summary>
        /// <param name="localDirectoryPath">The path to the local directory</param>
        /// <returns>Pending change list</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
        public ChangeList GetDirectoryHistory(string localDirectoryPath)
        {
            if (String.IsNullOrEmpty(localDirectoryPath))
            {
                throw new ArgumentNullException("localDirectoryPath");
            }

            ChangeList list = new ChangeList();
            if (this.workspace.IsLocalPathMapped(localDirectoryPath))
            {
                IEnumerable changeSetsEnum = this.versionControlServer.QueryHistory(localDirectoryPath, LatestVersionSpec.Latest,
                    0, RecursionType.Full, null, null, null, Int32.MaxValue, true, false);

                try
                {
                    foreach (Changeset cs in changeSetsEnum)
                    {
                        list.Add(new Change(cs, localDirectoryPath));
                    }
                }
                catch (Microsoft.TeamFoundation.VersionControl.Client.ItemNotFoundException)
                {
                    //it is ok to catch this exception in case item is added to version control but not checked in yet.
                }
            }
            return list;
        }

        /// <summary>
        /// Get details of a ChangeSet 
        /// </summary>
        /// <param name="changeSet">ChangeSet nuber</param>
        /// <returns>ChangeList bounds to ChangeSet</returns>
        public ChangeList GetByChangeSet(int changeSet)
        {
            ChangeList list = new ChangeList();
            Changeset cs = this.versionControlServer.GetChangeset(changeSet);
            if (cs != null)
            {
                foreach (Microsoft.TeamFoundation.VersionControl.Client.Change c in cs.Changes)
                {
                    list.Add(new Change(cs, c));
                }
            }

            return list;
        }

        /// <summary>
        /// Get checked-in changesets
        /// </summary>
        /// <param name="path">Given paht</param>
        /// <returns>ChangeSetList</returns>
        public ChangeSetList GetChangeSets(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            IEnumerable changeSetsEnum = this.versionControlServer.QueryHistory(path, LatestVersionSpec.Latest,
                0, RecursionType.Full, null, null, null, Int32.MaxValue, true, false);

            ChangeSetList list = new ChangeSetList();
            foreach (Changeset cs in changeSetsEnum)
            {
                list.Add(new ChangeSet(cs));
            }

            return list;
        }

        /// <summary>
        /// Download server item to local file
        /// </summary>
        /// <param name="serverPath">server path</param>
        /// <param name="changeSet">change set</param>
        /// <param name="localFile">local file name </param>
        public void DownloadFile(string serverPath, int changeSet, string localFile)
        {
            if (String.IsNullOrEmpty(serverPath))
            {
                throw new ArgumentNullException("serverPath");
            }
            if (String.IsNullOrEmpty(localFile))
            {
                throw new ArgumentNullException("localFile");
            }

            VersionSpec versionSpec = changeSet != 0 ? new ChangesetVersionSpec(changeSet) : VersionSpec.Latest;

            this.versionControlServer.DownloadFile(serverPath, 0, versionSpec, localFile);
        }

        /// <summary>
        /// Download the directory at specific version
        /// </summary>
        /// <param name="localDirectoryPath">The local directory path</param>
        /// <param name="changeSet">The change set</param>
        /// <param name="targetDirectory">The target directory, where the files will be extracted.</param>
        public void DownloadDirectory(string localDirectoryPath, int changeSet, string targetDirectory)
        {
            if (String.IsNullOrEmpty(localDirectoryPath))
            {
                throw new ArgumentNullException("localDirectoryPath");
            }
            if (String.IsNullOrEmpty(targetDirectory))
            {
                throw new ArgumentNullException("targetDirectory");
            }

            string serverPath = this.workspace.TryGetServerItemForLocalItem(localDirectoryPath).Trim();

            VersionSpec versionSpec = changeSet != 0 ? new ChangesetVersionSpec(changeSet) : VersionSpec.Latest;

            ItemSet items = this.versionControlServer.GetItems(serverPath, versionSpec, RecursionType.Full, DeletedState.NonDeleted, Microsoft.TeamFoundation.VersionControl.Client.ItemType.File);

            foreach(Item item in items.Items)
            {
                // Compute the relative path
                string relativePath = item.ServerItem.Replace(serverPath, String.Empty);
                
                // Replace server to client characters and trim the first backslash
                relativePath = relativePath.Replace('/', '\\').TrimStart('\\');

                // Create the temporary file path.
                string tempFilePath = Path.Combine(targetDirectory, relativePath);
                item.DownloadFile(tempFilePath);
            }
        }
        
        /// <summary>
        /// Delete
        /// </summary>
        /// <param name="path">local path</param>
        public PendOperationCommandResult Delete(string path)
        {
            if (String.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }
            WorkspaceProxy.ERROR_Queue.Clear();
            WorkspaceProxy.PENDINGCHANGE_Queue.Clear();
            PendOperationCommandResult result = new PendOperationCommandResult();

            result.NumberOfOperations = this.workspace.PendDelete(path, RecursionType.Full);

            //PendDelete use NonFatalError Event
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }
            foreach (PendingChangeOutput output in WorkspaceProxy.PENDINGCHANGE_Queue)
            {
                result.Add(output);
            }
            return result;
        }

        /// <summary>
        /// GetInstanceInfo
        /// </summary>
        /// <param name="workspacePath">Given workspace path</param>
        /// <returns>String representation of Team Foundation Server instance info</returns>
        public string GetInstanceInfo(string workspacePath)
        {
            if (String.IsNullOrEmpty(workspacePath))
                throw new ArgumentNullException("workspacePath");

            StringBuilder builder = new StringBuilder();
            builder.AppendLine("\n***Team Foundation Server Instance Info***\n");
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "{0} {1}", "AuthorizedUserName:    ", this.TeamProjectCollection.AuthorizedIdentity.DisplayName));
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "{0} {1}", "HasAuthenticated:      ", this.TeamProjectCollection.HasAuthenticated));
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "{0} {1}", "Server Name:           ", this.TeamProjectCollection.Name));
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "{0} {1}", "Server Uri:            ", this.TeamProjectCollection.Uri));
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "{0} {1}", "ClientCacheDirectory:  ", this.TeamProjectCollection.ClientCacheDirectoryForInstance));

            WorkspaceInfo workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(workspacePath);
            Debug.Assert(workspaceInfo != null, "Given workspace must exists");
            builder.AppendLine("\n***Workspace Instance Info***\n");
            builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "Display Name: {0}", workspaceInfo.DisplayName));
            foreach (string mapPath in workspaceInfo.MappedPaths)
            {
                builder.AppendLine(String.Format(CultureInfo.CurrentCulture, "Mapped Path: {0}", mapPath));
            }

            return builder.ToString();

        }

        /// <summary>
        /// Get server item(s) to local repository
        /// </summary>
        /// <param name="paths">Item paths to sync</param>
        /// <param name="changeSet">ChangeSet number</param>
        /// <param name="forceSync">Force sync</param>
        /// <returns>Command result</returns>
        public GetCommandResult Get(string paths, int changeSet, bool forceSync)
        {
            if (String.IsNullOrEmpty(paths))
            {
                throw new ArgumentNullException("paths");
            }

            VersionSpec versionSpec = changeSet != 0 ? new ChangesetVersionSpec(changeSet) : VersionSpec.Latest;

            string[] items = paths.Split(';');
            List<GetRequest> getRequests = new List<GetRequest>(items.Length);
            foreach (String item in items)
            {
                //The last item should be empty string for current split notation
                if (item.Length > 0)
                {
                    GetRequest getRequest = new GetRequest(item, RecursionType.Full, versionSpec);
                    getRequests.Add(getRequest);
                }
            }

            GetOptions getOptions = GetOptions.None;
            if (forceSync)
            {
                //Forces all files to be retrieved, not just those that are out-of-date.
                //Overwrites writable files that are not checked out.
                getOptions = GetOptions.GetAll | GetOptions.Overwrite;
            }

            WorkspaceProxy.GET_Queue.Clear();
            WorkspaceProxy.ERROR_Queue.Clear();

            GetStatus getStatus = this.workspace.Get(getRequests.ToArray(), getOptions);

            GetCommandResult result = new GetCommandResult();
            result.Status = getStatus;

            foreach (GetCommandOutput output in WorkspaceProxy.GET_Queue)
            {
                result.Add(output);
            }
            foreach (NonFatalErrorOutput output in WorkspaceProxy.ERROR_Queue)
            {
                result.Add(output);
            }

            return result;
        }

        /// <summary>
        /// IsServerPathMapped
        /// </summary>
        /// <param name="localPath">local path</param>
        /// <returns>True or false</returns>
        public bool IsServerPathMapped(string localPath)
        {
            string serverPath = this.workspace.TryGetServerItemForLocalItem(localPath);

            if (!String.IsNullOrEmpty(serverPath))
            {
                return this.workspace.IsServerPathMapped(serverPath);
            }

            return false;
        }

        /// <summary>
        /// Returns true if the specified file is located in the Team Foundation Server database in a non-deleted state.
        /// </summary>
        /// <param name="localPath">local file path</param>
        /// <returns>True if exists on server, otherwise false</returns>
        public bool ServerItemExists(string localPath)
        {
            string serverPath = this.workspace.TryGetServerItemForLocalItem(localPath);

            if (!String.IsNullOrEmpty(serverPath))
            {
                return this.versionControlServer.ServerItemExists(serverPath, TeamFoundation.VersionControl.Client.ItemType.File);
            }

            return false;
        }

        /// <summary>
        /// Get Checked Out information
        /// </summary>
        /// <param name="path">local path</param>
        /// <returns>A contenated string that contains checkout information</returns>
        public string CheckedOutTo(string path)
        {
            StringBuilder users = new StringBuilder();

            PendingSet[] pendingSets = versionControlServer.QueryPendingSets(new string[] { path }, RecursionType.Full, null, null);
            if (pendingSets != null)
            {
                foreach (PendingSet set in pendingSets)
                {
                    users.AppendLine(String.Format(CultureInfo.CurrentCulture, "Workspace{0};{1} on {2}", set.Name, set.OwnerName, set.Computer));
                }
            }

            return users.ToString();
        }
        /// <summary>
        /// Set lock
        /// </summary>
        /// <param name="localPath">local path</param>
        public int SetLock(string localPath)
        {
            if (String.IsNullOrEmpty(localPath))
            {
                throw new ArgumentNullException("localPath");
            }

            return this.workspace.SetLock(localPath, LockLevel.Checkin);
        }
        /// <summary>
        /// Unlock
        /// </summary>
        /// <param name="localPath">local path</param>
        public int Unlock(string localPath)
        {
            if (String.IsNullOrEmpty(localPath))
            {
                throw new ArgumentNullException("localPath");
            }

            return this.workspace.SetLock(localPath, LockLevel.None);
        }

        /// <summary>
        /// ActionTextForPendingChange
        /// </summary>
        /// <param name="localPath">local path</param>
        /// <returns>action text</returns>
        public string ActionTextForPendingChange(string localPath)
        {
            if (String.IsNullOrEmpty(localPath))
            {
                throw new ArgumentNullException("localPath");
            }
            PendingChange[] changes = this.workspace.GetPendingChanges(localPath);
            if (changes != null && changes.Length > 0)
            {
                //should only contain one pending change
                ChangeType changeType = changes[0].ChangeType;

                //Strip merge change type, so result is something Ax supports ("edit" instead of "edit, merge")
                if (changeType.HasFlag(ChangeType.Merge))
                {
                    changeType ^= ChangeType.Merge;
                }

                return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(changeType);
            }

            return String.Empty;
        }

        /// <summary>
        /// ActionTextForChangeType
        /// </summary>
        /// <param name="axChangeType">Type of change</param>
        /// <returns>action text</returns>
        public static string ActionTextForChangeType(AXChangeType axChangeType)
        {
            switch (axChangeType)
            {
                case AXChangeType.Add: return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(ChangeType.Add);
                case AXChangeType.Edit: return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(ChangeType.Edit);
                case AXChangeType.Branch: return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(ChangeType.Branch);
                case AXChangeType.Rename: return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(ChangeType.Rename | ChangeType.Edit);
                case AXChangeType.Delete: return Microsoft.TeamFoundation.VersionControl.Client.PendingChange.GetLocalizedStringForChangeType(ChangeType.Delete);
            }

            return String.Empty;
        }

        /// <summary>
        /// Converts a server item path to a local file path according to current workspace mapping.
        /// </summary>
        /// <param name="serverItem">Path to an item on the server</param>
        /// <returns>Local path according to current workspace mapping</returns>
        public string GetLocalItemForServerItem(string serverItem)
        {
            string localItem = this.workspace.TryGetLocalItemForServerItem(serverItem);

            return String.IsNullOrEmpty(localItem) ? String.Empty : localItem;
        }

        #endregion

        #region Private methods

        private static string CombineServerPaths(string rootPath, string subPath)
        {
            string prefix = rootPath.TrimEnd(SERVER_PATH_DELIMETER);
            string postfix = subPath.TrimStart(SERVER_PATH_DELIMETER);
            postfix = postfix.TrimEnd(SERVER_PATH_DELIMETER);
            string result = String.Concat(prefix, SERVER_PATH_DELIMETER, postfix);

            return result.TrimEnd(SERVER_PATH_DELIMETER);
        }

        private PendingChange[] SearchAndConvert(Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.ChangeList changeList)
        {
            List<PendingChange> changes = new List<PendingChange>(changeList.Count);

            foreach (Change change in changeList)
            {
                Debug.Assert(!String.IsNullOrEmpty(change.LocalItem), "LocalItem has to be presented.");
                PendingChange[] pendingChange = this.workspace.GetPendingChanges(change.LocalItem);
                changes.AddRange(pendingChange);
            }

            Debug.Assert(changes.Count == changeList.Count, "All item from changelist should have a match pending change from workspace");

            return changes.ToArray();
        }

        private Workspace TryGetWorkspace(string localPath)
        {
            Workspace localWorkspace = this.versionControlServer.TryGetWorkspace(localPath);

            if (localWorkspace != null)
            {
                return localWorkspace;
            }

            // Try to find public workspaces
            Workspace[] workspaces = this.versionControlServer.QueryWorkspaces(null, null, Environment.MachineName);

            foreach (Workspace tmpWorkspace in workspaces)
            {
                if (tmpWorkspace.IsLocalPathMapped(localPath) && tmpWorkspace.HasUsePermission)
                {
                    return tmpWorkspace;
                }
            }

            return null;
        }

        #endregion

        #region Event Handler

        //Pend calls (e.g., PendEdit), Get(), etc. will use the NonFatalErrorEvent
        internal static void OnNonFatalError(Object sender, ExceptionEventArgs e)
        {
            WorkspaceProxy.ERROR_Queue.Enqueue(new NonFatalErrorOutput(e));
        }

        internal static void OnGetting(Object sender, GettingEventArgs e)
        {
            WorkspaceProxy.GET_Queue.Enqueue(new GetCommandOutput(e));
        }

        internal static void OnNewPendingChange(Object sender, PendingChangeEventArgs e)
        {
            WorkspaceProxy.PENDINGCHANGE_Queue.Enqueue(new PendingChangeOutput(e));
        }

        internal static void OnCommitCheckin(object sender, CommitCheckinEventArgs e)
        {
            WorkspaceProxy.COMMIT_Queue.Enqueue(new CommitCheckInOutput(e));
        }

        internal static void OnConflict(object sender, ConflictEventArgs e)
        {
            WorkspaceProxy.CONFLICT_Queue.Enqueue(new ConflictOutput(e));
        }

        internal static void OnUndonePendingChange(object sender, PendingChangeEventArgs e)
        {
            WorkspaceProxy.PENDINGCHANGE_Queue.Enqueue(new PendingChangeOutput(e));
        }

        #endregion

        #region Support shelveset
        //V6.0: Support shelveset
        ///// <summary>
        ///// Get current checked out items from given shelve set id
        ///// </summary>
        ///// <param name="shelvesetId">Given shelve set id</param>
        ///// <returns>Pending change list</returns>
        //public ChangeList GetCheckedOutItems(string shelvesetId)
        //{
        //    ChangeList changeList = new ChangeList();

        //    PendingSet[] sets = versionControlServer.QueryShelvedChanges(shelvesetId, this.workspace.OwnerName);

        //    foreach (PendingSet set in sets)
        //    {
        //        foreach (PendingChange change in set.PendingChanges)
        //        {
        //            changeList.Add(new Change(workspace, change));
        //        }
        //    }

        //    return changeList;

        //}
        ///// <summary>
        ///// PendingShelvesets
        ///// </summary>
        ///// <returns>ArrayList that composes of pending shelvesets</returns>
        //public ArrayList PendingShelvesets
        //{
        //    get
        //    {
        //        ArrayList list = new ArrayList();
        //        Shelveset[] sets = versionControlServer.QueryShelvesets(null, this.workspace.OwnerName);

        //        foreach (Shelveset set in sets)
        //        {
        //            list.Add(new Shelve(set));
        //        }

        //        return list;
        //    }
        //}


        #endregion

    }
    /// <summary>
    /// Type of change understood by AX
    /// </summary>
    public enum AXChangeType
    {
        /// <summary>Add item to version control</summary>
        Add = 0,
        /// <summary>Edit item</summary>
        Edit = 1,
        /// <summary>Branch item</summary>
        Branch = 2,
        /// <summary>Rename item</summary>
        Rename = 3,
        /// <summary>Delete item from version control</summary>
        Delete = 4,
    }

}
